day 5¶
このノートブックの実行例はこちら(HTML版)で確認できます
0. はじめに¶
ページ上部のメニューバーにある Kernel メニューをクリックし、プルダウンメニューから [Change Kernel ...] を選び、gssm2024:Python を選択してください。
ノートブック上部の右隅に表示されたカーネル名が gssm2024:Python になっていることを確認してください。
1. テキスト分析 (実践編)¶
1.0 事前準備¶
1.0.1 定義済み関数の読み込み¶
以下のセルを修正せずに実行してください
In [1]:
import warnings
warnings.simplefilter('ignore')
import gssm_utils
%matplotlib inline
1.0.1 データのダウンロード (前回ダウンロード済みのためスキップ)¶
以下のデータがダウンロード済みです
| ファイル名 | 件数 | データセット | 備考 |
|---|---|---|---|
| rakuten-1000-2023-2024.xlsx.zip | 10,000 | •レジャー+ビジネスの 10エリア •エリアごと 1,000件 (ランダムサンプリング) •期間: 2023/1~2024 GW明け |
本講義の全体を通して使用する |
In [2]:
# もし、再度ダウンロードが必要な場合は残りの行のコメントマーク「#」を除去して、このセルを再実行してください
# FILE_ID = "1EeCuDrfKdlsMxG9p3Ot7TIxfV9_f2smY"
# !gdown --id {FILE_ID}
# !unzip -o rakuten-1000-2023-2024.xlsx.zip
1.0.2 データの読み込み (DataFrame型)¶
In [3]:
import numpy as np
import pandas as pd
all_df = pd.read_excel("rakuten-1000-2023-2024.xlsx")
print(all_df.shape)
display(all_df.head())
(10000, 18)
| カテゴリー | エリア | 施設番号 | 施設名 | コメント | 総合 | サービス | 立地 | 部屋 | 設備・アメニティ | 風呂 | 食事 | 旅行の目的 | 同伴者 | 宿泊年月 | 投稿者 | 年代 | 性別 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | A_レジャー | 01_登別 | 80732 | 登別カルルス温泉 湯元オロフレ荘 | 彼のリクエストでカルルス温泉に宿泊させていただきました。北海道ラブ割利用でお得に宿泊できる分... | 5 | 5 | 4 | 5 | 5.0 | 5.0 | 5.0 | レジャー | 恋人 | 45231 | 投稿者 | na | na |
| 1 | A_レジャー | 01_登別 | 149334 | 天然温泉 幸鐘の湯 ドーミーイン東室蘭(ドーミーイン・御宿野乃 ホテルズグループ) | 室蘭方面の旅行の時には何度か宿泊しています。ドーミーインのサービスはとても良くて大好きです。... | 4 | 5 | 5 | 2 | 5.0 | 5.0 | 5.0 | レジャー | 家族 | 45413 | 投稿者 | na | na |
| 2 | A_レジャー | 01_登別 | 109022 | 心のリゾート 海の別邸ふる川 | もう一度行きたくなる宿でした。部屋もワンランクアップしていただき感謝しています。お風呂も最高... | 4 | 3 | 5 | 5 | 4.0 | 5.0 | 3.0 | レジャー | 一人 | 45261 | gaku0713 | 60代 | 男性 |
| 3 | A_レジャー | 01_登別 | 7506 | ホテルニューバジェット室蘭 | 居心地良く過ごせました | 4 | 4 | 4 | 4 | 4.0 | 4.0 | 3.0 | レジャー | 一人 | 45200 | 投稿者 | na | na |
| 4 | A_レジャー | 01_登別 | 37380 | 登別温泉 旅亭 花ゆら | 事前カード精算で露天風呂付特別和洋室を予約しましたが別の和室に案内されました。すぐ違うことに... | 1 | 2 | 4 | 2 | 2.0 | 4.0 | 3.0 | レジャー | 家族 | 45323 | 投稿者 | na | na |
1.0.3 「文書-抽出語」表 を作成する¶
コメント列から単語を抽出する (単語を品詞「名詞」「形容詞」「未知語」で絞り込む)
In [4]:
# 必要ライブラリのインポート
from collections import defaultdict
import MeCab
# mecab の初期化
tagger = MeCab.Tagger("-r ../tools/usr/local/etc/mecabrc --unk-feature 未知語")
# 単語頻度辞書の初期化
word_counts = defaultdict(lambda: 0)
# 抽出語情報リストの初期化
words = []
# 半角->全角変換マクロを定義する
ZEN = "".join(chr(0xff01 + i) for i in range(94))
HAN = "".join(chr(0x21 + i) for i in range(94))
HAN2ZEN = str.maketrans(HAN, ZEN)
# ストップワードを定義する
# stopwords = ['する', 'ある', 'ない', 'いう', 'もの', 'こと', 'よう', 'なる', 'ほう']
stopwords = ["湯畑"]
# データ1行ごとのループ
for index, row in all_df.iterrows():
# 半角->全角変換した後で, mecab で解析する
node = tagger.parseToNode(row["コメント"].translate(HAN2ZEN))
# 形態素ごとのループ
while node:
# 解析結果を要素ごとにバラす
features = node.feature.split(',')
# 品詞1 を取り出す
pos1 = features[0]
# 品詞2 を取り出す
pos2 = features[1] if len(features) > 1 else ""
# 原形 を取り出す
base = features[6] if len(features) > 6 else None
# 原型がストップワードに含まれない単語のみ抽出する
if base not in stopwords:
# 「名詞-一般」
if (pos1 == "名詞" and pos2 == "一般"):
base = base if base is not None else node.surface
postag = "名詞"
key = (base, postag)
# 単語頻度辞書をカウントアップする
word_counts[key] += 1
# 抽出語情報をリストに追加する
words.append([index + 1, base, postag, row["カテゴリー"], row["エリア"], key])
# 「形容動詞」
elif (pos1 == "名詞" and pos2 == "形容動詞語幹"):
base = base if base is not None else node.surface
base = f"{base}"
postag = "形容動詞"
key = (base, postag)
# 単語頻度辞書をカウントアップする
word_counts[key] += 1
# 抽出語情報をリストに追加する
words.append([index + 1, base, postag, row["カテゴリー"], row["エリア"], key])
# 「形容詞」
elif pos1 == "形容詞":
base = base if base is not None else node.surface
postag = "形容詞"
key = (base, postag)
# 単語頻度辞書をカウントアップする
word_counts[key] += 1
# 抽出語情報をリストに追加する
words.append([index + 1, base, postag, row["カテゴリー"], row["エリア"], key])
# 「未知語」
elif pos1 == "未知語":
base = base if base is not None else node.surface
postag = "未知語"
key = (base, postag)
# 単語頻度辞書をカウントアップする
word_counts[key] += 1
# 抽出語情報をリストに追加する
words.append([index + 1, base, postag, row["カテゴリー"], row["エリア"], key])
# 次の形態素へ
node = node.next
# DataFrme 型に整える
columns = [
"文書ID",
# "単語ID",
"表層",
"品詞",
"カテゴリー",
"エリア",
"dict_key",
]
docs_df = pd.DataFrame(words, columns=columns)
# DataFrame を表示する
print(docs_df.shape)
display(docs_df.head())
(158822, 6)
| 文書ID | 表層 | 品詞 | カテゴリー | エリア | dict_key | |
|---|---|---|---|---|---|---|
| 0 | 1 | リクエスト | 名詞 | A_レジャー | 01_登別 | (リクエスト, 名詞) |
| 1 | 1 | 温泉 | 名詞 | A_レジャー | 01_登別 | (温泉, 名詞) |
| 2 | 1 | ラブ | 名詞 | A_レジャー | 01_登別 | (ラブ, 名詞) |
| 3 | 1 | 割 | 名詞 | A_レジャー | 01_登別 | (割, 名詞) |
| 4 | 1 | 得 | 名詞 | A_レジャー | 01_登別 | (得, 名詞) |
抽出語の出現頻度をカウントする
In [5]:
# 「文書-抽出語」 表から単語の出現回数をカウントする
word_list = []
for i, (k, v) in enumerate(sorted(word_counts.items(), key=lambda x:x[1], reverse=True)):
word_list.append((i, k[0], v, k))
# DataFrame 型に整える
columns = [
"単語ID",
"表層",
"出現頻度",
"dict_key"
]
# DataFrame を表示する
word_counts_df = pd.DataFrame(word_list, columns=columns)
print(word_counts_df.shape)
display(word_counts_df.head(10))
(9060, 4)
| 単語ID | 表層 | 出現頻度 | dict_key | |
|---|---|---|---|---|
| 0 | 0 | 部屋 | 6768 | (部屋, 名詞) |
| 1 | 1 | 良い | 5397 | (良い, 形容詞) |
| 2 | 2 | ホテル | 3001 | (ホテル, 名詞) |
| 3 | 3 | 風呂 | 2692 | (風呂, 名詞) |
| 4 | 4 | ない | 2392 | (ない, 形容詞) |
| 5 | 5 | 美味しい | 2294 | (美味しい, 形容詞) |
| 6 | 6 | 温泉 | 1854 | (温泉, 名詞) |
| 7 | 7 | スタッフ | 1677 | (スタッフ, 名詞) |
| 8 | 8 | 立地 | 1532 | (立地, 名詞) |
| 9 | 9 | よい | 1517 | (よい, 形容詞) |
1.1 カテゴリーやエリアごとのユーザーの注目ポイントを押さえる¶
2.1.1 「文書-抽出語」表の作成¶
「文書-抽出語」表を作成する (出現回数 Top 1000語)
In [6]:
# 「単語出現回数」 表から出現回数Top 1000語のみ抽出する
word_counts_1000_df = word_counts_df[0:1000]
# 「文書-抽出語」 表も出現回数Top 150語のみに絞り込む
merged_df = pd.merge(docs_df, word_counts_1000_df, how="inner", on="dict_key", suffixes=["", "_right"])
docs_1000_df = merged_df[["文書ID", "単語ID", "表層", "品詞", "カテゴリー", "エリア", "dict_key"]]
# 「カテゴリー,エリア」でクロス集計する
cross_1000_df = pd.crosstab(
[
docs_1000_df['カテゴリー'],
docs_1000_df['エリア'],
docs_1000_df['文書ID']
],
docs_1000_df['単語ID'], margins=False
)
cross_1000_df.columns = word_counts_1000_df["表層"]
「文書-抽出語」表を {0,1} に変換する
In [7]:
# 「文書-抽出語」 表を {0,1} に変換する
cross_1000_df[cross_1000_df > 0] = 1
# DataFrame を表示する
print(cross_1000_df.shape)
display(cross_1000_df)
(9914, 1000)
| 表層 | 部屋 | 良い | ホテル | 風呂 | ない | 美味しい | 温泉 | スタッフ | 立地 | よい | ... | ほこり | 豊か | カラオケ | 支配人 | 頻繁 | 感覚 | 枕元 | コンサート | 店舗 | 人柄 | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| カテゴリー | エリア | 文書ID | |||||||||||||||||||||
| A_レジャー | 01_登別 | 1 | 1 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 2 | 1 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 3 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 4 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 5 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| B_ビジネス | 10_福岡 | 9996 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 9997 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 9998 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 9999 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 10000 | 0 | 1 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
9914 rows × 1000 columns
2.1.2 共起行列を作成する (外部変数-抽出語)¶
In [8]:
# 「カテゴリー」のクロス集計と「エリア」のクロス集計を連結する
aggregate_df = pd.concat(
[
cross_1000_df.groupby(level='カテゴリー').sum(),
cross_1000_df.groupby(level='エリア').sum()
]
)
# DataFrame を表示する
print(aggregate_df.shape)
display(aggregate_df)
(12, 1000)
| 表層 | 部屋 | 良い | ホテル | 風呂 | ない | 美味しい | 温泉 | スタッフ | 立地 | よい | ... | ほこり | 豊か | カラオケ | 支配人 | 頻繁 | 感覚 | 枕元 | コンサート | 店舗 | 人柄 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| A_レジャー | 2367 | 2134 | 789 | 1498 | 957 | 1486 | 1290 | 916 | 548 | 677 | ... | 4 | 16 | 16 | 13 | 11 | 12 | 5 | 1 | 1 | 17 |
| B_ビジネス | 2213 | 1705 | 1368 | 652 | 789 | 542 | 113 | 505 | 942 | 578 | ... | 11 | 4 | 3 | 3 | 8 | 9 | 15 | 18 | 19 | 3 |
| 01_登別 | 433 | 422 | 173 | 296 | 193 | 260 | 271 | 149 | 47 | 111 | ... | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 2 |
| 02_草津 | 489 | 468 | 161 | 376 | 198 | 305 | 311 | 177 | 146 | 134 | ... | 1 | 3 | 4 | 3 | 8 | 1 | 0 | 0 | 0 | 5 |
| 03_箱根 | 544 | 454 | 158 | 331 | 199 | 373 | 234 | 222 | 69 | 156 | ... | 2 | 5 | 6 | 3 | 0 | 4 | 2 | 0 | 0 | 4 |
| 04_道後 | 408 | 380 | 228 | 190 | 157 | 177 | 231 | 130 | 184 | 135 | ... | 0 | 0 | 4 | 1 | 0 | 2 | 2 | 0 | 0 | 0 |
| 05_湯布院 | 493 | 410 | 69 | 305 | 210 | 371 | 243 | 238 | 102 | 141 | ... | 0 | 7 | 2 | 6 | 3 | 5 | 1 | 1 | 1 | 6 |
| 06_札幌 | 461 | 359 | 278 | 126 | 138 | 135 | 24 | 95 | 191 | 92 | ... | 1 | 1 | 0 | 1 | 3 | 4 | 2 | 4 | 3 | 1 |
| 07_名古屋 | 430 | 337 | 267 | 123 | 155 | 104 | 30 | 99 | 164 | 130 | ... | 1 | 0 | 1 | 0 | 1 | 0 | 4 | 2 | 5 | 0 |
| 08_東京 | 398 | 345 | 276 | 119 | 162 | 82 | 9 | 100 | 172 | 113 | ... | 3 | 0 | 0 | 1 | 0 | 1 | 5 | 1 | 1 | 1 |
| 09_大阪 | 459 | 316 | 262 | 139 | 151 | 100 | 25 | 104 | 184 | 122 | ... | 5 | 1 | 2 | 1 | 3 | 1 | 2 | 4 | 2 | 1 |
| 10_福岡 | 465 | 348 | 285 | 145 | 183 | 121 | 25 | 107 | 231 | 121 | ... | 1 | 2 | 0 | 0 | 1 | 3 | 2 | 7 | 8 | 0 |
12 rows × 1000 columns
2.1.3 Jaccard 係数を求める (外部変数-抽出語)¶
In [9]:
# 抽出語の出現回数を取得する
word_counts = cross_1000_df.sum(axis=0).values
# 属性(外部変数)出現数を取得する
attr_counts = np.hstack(
[
all_df.value_counts('カテゴリー').values,
all_df.value_counts('エリア').values
]
)
# 共起行列の中身を Jaccard 係数に入れ替える
jaccard_attrs_df = gssm_utils.jaccard_attrs_coef(aggregate_df, attr_counts, word_counts, total=10000, conditional=False)
# DataFrame を表示する
print(jaccard_attrs_df.shape)
display(jaccard_attrs_df)
(12, 1000)
| 表層 | 部屋 | 良い | ホテル | 風呂 | ない | 美味しい | 温泉 | スタッフ | 立地 | よい | ... | ほこり | 豊か | カラオケ | 支配人 | 頻繁 | 感覚 | 枕元 | コンサート | 店舗 | 人柄 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| A_レジャー | 0.328157 | 0.318270 | 0.000000 | 0.265039 | 0.165314 | 0.268134 | 0.252298 | 0.166394 | 0.000000 | 0.121370 | ... | 0.000000 | 0.003197 | 0.003198 | 0.002598 | 0.002196 | 0.002396 | 0.000000 | 0.000000 | 0.000000 | 0.003398 |
| B_ビジネス | 0.000000 | 0.000000 | 0.236310 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.169791 | 0.000000 | ... | 0.002198 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.002997 | 0.003599 | 0.003799 | 0.000000 |
| 01_登別 | 0.000000 | 0.095540 | 0.000000 | 0.103714 | 0.075597 | 0.093931 | 0.127111 | 0.065581 | 0.000000 | 0.000000 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
| 02_草津 | 0.096052 | 0.107069 | 0.000000 | 0.135544 | 0.077708 | 0.112009 | 0.148662 | 0.078877 | 0.000000 | 0.063178 | ... | 0.000000 | 0.002950 | 0.003941 | 0.002962 | 0.007913 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.004926 |
| 03_箱根 | 0.108022 | 0.103535 | 0.000000 | 0.117418 | 0.078131 | 0.140490 | 0.107884 | 0.100955 | 0.000000 | 0.074321 | ... | 0.001974 | 0.004926 | 0.005923 | 0.002962 | 0.000000 | 0.003933 | 0.000000 | 0.000000 | 0.000000 | 0.003937 |
| 04_道後 | 0.000000 | 0.000000 | 0.077842 | 0.000000 | 0.000000 | 0.000000 | 0.106354 | 0.000000 | 0.079792 | 0.063679 | ... | 0.000000 | 0.000000 | 0.003941 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
| 05_湯布院 | 0.096914 | 0.092572 | 0.000000 | 0.107206 | 0.082808 | 0.139631 | 0.112500 | 0.109024 | 0.000000 | 0.066698 | ... | 0.000000 | 0.006910 | 0.001967 | 0.005941 | 0.002953 | 0.004921 | 0.000000 | 0.000000 | 0.000000 | 0.005917 |
| 06_札幌 | 0.090057 | 0.000000 | 0.096561 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.083080 | 0.000000 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.002953 | 0.003933 | 0.000000 | 0.003941 | 0.002950 | 0.000000 |
| 07_名古屋 | 0.000000 | 0.000000 | 0.092388 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.070507 | 0.061176 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.003937 | 0.001967 | 0.004926 | 0.000000 |
| 08_東京 | 0.000000 | 0.000000 | 0.095800 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.074202 | 0.000000 | ... | 0.002964 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.004926 | 0.000000 | 0.000000 | 0.000000 |
| 09_大阪 | 0.089631 | 0.000000 | 0.090501 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.079792 | 0.000000 | ... | 0.004950 | 0.000000 | 0.001967 | 0.000000 | 0.002953 | 0.000000 | 0.000000 | 0.003941 | 0.000000 | 0.000000 |
| 10_福岡 | 0.090909 | 0.000000 | 0.099234 | 0.000000 | 0.071401 | 0.000000 | 0.000000 | 0.000000 | 0.102258 | 0.000000 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.002947 | 0.000000 | 0.006917 | 0.007905 | 0.000000 |
12 rows × 1000 columns
2.1.4 特徴語ランキング¶
In [10]:
# 「カテゴリ」や「エリア」ごとに Jaccard 係数のTop 10語を抽出する
df_list = []
for index, row in jaccard_attrs_df.iterrows():
df_list.append(row.iloc[np.argsort(row.values)[::-1][:10]].reset_index())
# 「カテゴリ」や「エリア」ごとの Jaccard 係数のTop 10 を横方向に連結した DetaFrame を作成する
ranking_df = pd.DataFrame(pd.concat(df_list, axis=1))
ranking_df.columns = np.array([c for pair in [[x,f"jaccard"] for x in jaccard_attrs_df.index] for c in pair], dtype='object')
# DataFrame を表示する
display(ranking_df)
| A_レジャー | jaccard | B_ビジネス | jaccard | 01_登別 | jaccard | 02_草津 | jaccard | 03_箱根 | jaccard | ... | 06_札幌 | jaccard | 07_名古屋 | jaccard | 08_東京 | jaccard | 09_大阪 | jaccard | 10_福岡 | jaccard | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 部屋 | 0.328157 | ホテル | 0.236310 | 温泉 | 0.127111 | 温泉 | 0.148662 | 美味しい | 0.140490 | ... | ホテル | 0.096561 | ホテル | 0.092388 | 駅 | 0.121037 | 駅 | 0.092697 | 立地 | 0.102258 |
| 1 | 良い | 0.318270 | 立地 | 0.169791 | 風呂 | 0.103714 | 風呂 | 0.135544 | 露天風呂 | 0.132890 | ... | 部屋 | 0.090057 | 駅 | 0.084774 | 便利 | 0.097297 | ホテル | 0.090501 | ホテル | 0.099234 |
| 2 | 美味しい | 0.268134 | 駅 | 0.153921 | 良い | 0.095540 | 宿 | 0.117160 | 風呂 | 0.117418 | ... | 立地 | 0.083080 | 便利 | 0.072372 | ホテル | 0.095800 | 部屋 | 0.089631 | 部屋 | 0.090909 |
| 3 | 風呂 | 0.265039 | 便利 | 0.145952 | 美味しい | 0.093931 | 美味しい | 0.112009 | 部屋 | 0.108022 | ... | 浴場 | 0.074408 | 立地 | 0.070507 | コンビニ | 0.082481 | 便利 | 0.083823 | 駅 | 0.085379 |
| 4 | 温泉 | 0.252298 | フロント | 0.115707 | バイキング | 0.084771 | 良い | 0.107069 | 温泉 | 0.107884 | ... | 便利 | 0.071806 | 近い | 0.070175 | 近い | 0.080283 | 立地 | 0.079792 | 便利 | 0.084402 |
| 5 | スタッフ | 0.166394 | 近い | 0.112383 | 夕食 | 0.084353 | 部屋 | 0.096052 | 宿 | 0.107331 | ... | 快適 | 0.065593 | 快適 | 0.069420 | 立地 | 0.074202 | フロント | 0.073434 | ない | 0.071401 |
| 6 | ない | 0.165314 | 綺麗 | 0.104125 | 種類 | 0.078832 | 湯 | 0.094293 | 良い | 0.103535 | ... | 広い | 0.063860 | フロント | 0.066524 | フロント | 0.067669 | 近い | 0.073314 | 綺麗 | 0.067352 |
| 7 | 宿 | 0.159968 | 快適 | 0.095608 | ない | 0.075597 | 最高 | 0.084139 | スタッフ | 0.100955 | ... | 駅 | 0.061681 | よい | 0.061176 | アメニティ | 0.063199 | 綺麗 | 0.063525 | フロント | 0.064810 |
| 8 | 露天風呂 | 0.132394 | コンビニ | 0.087794 | 最高 | 0.071951 | 夕食 | 0.081567 | 夕食 | 0.097068 | ... | フロント | 0.060832 | コンビニ | 0.060788 | 綺麗 | 0.057026 | 快適 | 0.060534 | 近い | 0.063953 |
| 9 | 夕食 | 0.124876 | アメニティ | 0.076083 | 残念 | 0.071800 | スタッフ | 0.078877 | 残念 | 0.078534 | ... | 近い | 0.056582 | 綺麗 | 0.059184 | 快適 | 0.056147 | コンビニ | 0.057961 | バス | 0.058943 |
10 rows × 24 columns
2.1.5 ワードクラウド¶
In [11]:
import matplotlib.pyplot as plt
%matplotlib inline
# サブルーチン
def sort_and_plot(name, group):
# 「カテゴリー」ごとに Jaccard 係数でソートする
sorted_columns = np.argsort(jaccard_attrs_df.loc[name].values)[::-1][:75]
# Jaccard 係数Top 75語をソートして抽出する
group_cross_df = group.iloc[:,sorted_columns]
# プロットする
ax = fig.add_subplot(4, 3, i+1)
gssm_utils.plot_wordcloud_ax(ax, " ".join(group_cross_df.columns))
ax.set_title(name)
# プロットの準備
fig = plt.figure(figsize=(12, 9))
i = 0
# カテゴリごとのループ
for name, group in cross_1000_df.groupby(level='カテゴリー'):
# サブルーチンを呼ぶ
sort_and_plot(name, group)
i += 1
# エリアごとのループ
for sub_name, sub_group in group.groupby(level='エリア'):
# サブルーチンを呼ぶ
sort_and_plot(sub_name, sub_group)
i += 1
# プロットの仕上げ
plt.tight_layout()
plt.show()
2.1.6 共起ネットワーク図 (カテゴリ)¶
In [12]:
# 抽出語の出現回数を取得する
word_counts = cross_1000_df.sum(axis=0).values
# 属性(外部変数)出現数を取得する
attr_counts = np.hstack(
[
all_df.value_counts('カテゴリー').values,
]
)
# カテゴリのみの共起行列(共起度数)を取得する
df = aggregate_df.loc[["A_レジャー","B_ビジネス"],:]
# 共起行列(共起度数)で共起ネットワーク図を作成する
gssm_utils.plot_attrs_network(df, attr_counts, word_counts, np.sort(df.values.reshape(-1))[::-1][60], width=8, height=8)
2.1.7 共起ネットワーク図 (エリア)¶
In [13]:
# 抽出語の出現回数を取得する
word_counts = cross_1000_df.sum(axis=0).values
# 属性(外部変数)出現数を取得する
attr_counts = np.hstack(
[
all_df.value_counts('エリア').values,
]
)
# カテゴリのみの共起行列(共起度数)を取得する
df = aggregate_df.iloc[2:,:]
# 共起行列((共起度数)で共起ネットワーク図を作成する
gssm_utils.plot_attrs_network(df, attr_counts, word_counts, np.sort(df.values.reshape(-1))[::-1][120], width=8, height=8)
1.2 カテゴリーやエリアごとのユーザーの注目ポイントの評価の違いを見つける¶
1.1.1 「文書-抽出語」表の作成¶
「文書-抽出語」表を作成する (出現回数 Top 1000語)
In [14]:
# 「単語出現回数」 表から出現回数Top 1000語のみ抽出する
word_counts_1000_df = word_counts_df[0:1000]
# 「文書-抽出語」 表も出現回数Top 150語のみに絞り込む
merged_df = pd.merge(docs_df, word_counts_1000_df, how="inner", on="dict_key", suffixes=["", "_right"])
docs_1000_df = merged_df[["文書ID", "単語ID", "表層", "品詞", "カテゴリー", "エリア", "dict_key"]]
# 「カテゴリー,エリア」でクロス集計する
cross_1000_df = pd.crosstab(
[
docs_1000_df['カテゴリー'],
docs_1000_df['エリア'],
docs_1000_df['文書ID']
],
docs_1000_df['単語ID'], margins=False
)
cross_1000_df.columns = word_counts_1000_df["表層"]
「文書-抽出語」表を {0,1} に変換する
In [15]:
# 「文書-抽出語」 表を {0,1} に変換する
cross_1000_df[cross_1000_df > 0] = 1
# DataFrame を表示する
print(cross_1000_df.shape)
display(cross_1000_df)
(9914, 1000)
| 表層 | 部屋 | 良い | ホテル | 風呂 | ない | 美味しい | 温泉 | スタッフ | 立地 | よい | ... | ほこり | 豊か | カラオケ | 支配人 | 頻繁 | 感覚 | 枕元 | コンサート | 店舗 | 人柄 | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| カテゴリー | エリア | 文書ID | |||||||||||||||||||||
| A_レジャー | 01_登別 | 1 | 1 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 2 | 1 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 3 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 4 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 5 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| B_ビジネス | 10_福岡 | 9996 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 9997 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 9998 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 9999 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 10000 | 0 | 1 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
9914 rows × 1000 columns
1.1.2 共起行列を作成する (外部変数-抽出語)¶
In [16]:
# 「カテゴリー」のクロス集計と「エリア」のクロス集計を連結する
aggregate_df = pd.concat(
[
cross_1000_df.groupby(level='カテゴリー').sum(),
cross_1000_df.groupby(level='エリア').sum()
]
)
# DataFrame を表示する
print(aggregate_df.shape)
display(aggregate_df)
(12, 1000)
| 表層 | 部屋 | 良い | ホテル | 風呂 | ない | 美味しい | 温泉 | スタッフ | 立地 | よい | ... | ほこり | 豊か | カラオケ | 支配人 | 頻繁 | 感覚 | 枕元 | コンサート | 店舗 | 人柄 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| A_レジャー | 2367 | 2134 | 789 | 1498 | 957 | 1486 | 1290 | 916 | 548 | 677 | ... | 4 | 16 | 16 | 13 | 11 | 12 | 5 | 1 | 1 | 17 |
| B_ビジネス | 2213 | 1705 | 1368 | 652 | 789 | 542 | 113 | 505 | 942 | 578 | ... | 11 | 4 | 3 | 3 | 8 | 9 | 15 | 18 | 19 | 3 |
| 01_登別 | 433 | 422 | 173 | 296 | 193 | 260 | 271 | 149 | 47 | 111 | ... | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 2 |
| 02_草津 | 489 | 468 | 161 | 376 | 198 | 305 | 311 | 177 | 146 | 134 | ... | 1 | 3 | 4 | 3 | 8 | 1 | 0 | 0 | 0 | 5 |
| 03_箱根 | 544 | 454 | 158 | 331 | 199 | 373 | 234 | 222 | 69 | 156 | ... | 2 | 5 | 6 | 3 | 0 | 4 | 2 | 0 | 0 | 4 |
| 04_道後 | 408 | 380 | 228 | 190 | 157 | 177 | 231 | 130 | 184 | 135 | ... | 0 | 0 | 4 | 1 | 0 | 2 | 2 | 0 | 0 | 0 |
| 05_湯布院 | 493 | 410 | 69 | 305 | 210 | 371 | 243 | 238 | 102 | 141 | ... | 0 | 7 | 2 | 6 | 3 | 5 | 1 | 1 | 1 | 6 |
| 06_札幌 | 461 | 359 | 278 | 126 | 138 | 135 | 24 | 95 | 191 | 92 | ... | 1 | 1 | 0 | 1 | 3 | 4 | 2 | 4 | 3 | 1 |
| 07_名古屋 | 430 | 337 | 267 | 123 | 155 | 104 | 30 | 99 | 164 | 130 | ... | 1 | 0 | 1 | 0 | 1 | 0 | 4 | 2 | 5 | 0 |
| 08_東京 | 398 | 345 | 276 | 119 | 162 | 82 | 9 | 100 | 172 | 113 | ... | 3 | 0 | 0 | 1 | 0 | 1 | 5 | 1 | 1 | 1 |
| 09_大阪 | 459 | 316 | 262 | 139 | 151 | 100 | 25 | 104 | 184 | 122 | ... | 5 | 1 | 2 | 1 | 3 | 1 | 2 | 4 | 2 | 1 |
| 10_福岡 | 465 | 348 | 285 | 145 | 183 | 121 | 25 | 107 | 231 | 121 | ... | 1 | 2 | 0 | 0 | 1 | 3 | 2 | 7 | 8 | 0 |
12 rows × 1000 columns
1.1.3 Jaccard 係数を求める (外部変数-抽出語)¶
In [17]:
# 抽出語の出現回数を取得する
word_counts = cross_1000_df.sum(axis=0).values
# 属性(外部変数)出現数を取得する
attr_counts = np.hstack(
[
all_df.value_counts('カテゴリー').values,
all_df.value_counts('エリア').values
]
)
# 共起行列の中身を Jaccard 係数に入れ替える
jaccard_attrs_df = gssm_utils.jaccard_attrs_coef(aggregate_df, attr_counts, word_counts, total=10000, conditional=False)
# DataFrame を表示する
print(jaccard_attrs_df.shape)
display(jaccard_attrs_df)
(12, 1000)
| 表層 | 部屋 | 良い | ホテル | 風呂 | ない | 美味しい | 温泉 | スタッフ | 立地 | よい | ... | ほこり | 豊か | カラオケ | 支配人 | 頻繁 | 感覚 | 枕元 | コンサート | 店舗 | 人柄 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| A_レジャー | 0.328157 | 0.318270 | 0.000000 | 0.265039 | 0.165314 | 0.268134 | 0.252298 | 0.166394 | 0.000000 | 0.121370 | ... | 0.000000 | 0.003197 | 0.003198 | 0.002598 | 0.002196 | 0.002396 | 0.000000 | 0.000000 | 0.000000 | 0.003398 |
| B_ビジネス | 0.000000 | 0.000000 | 0.236310 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.169791 | 0.000000 | ... | 0.002198 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.002997 | 0.003599 | 0.003799 | 0.000000 |
| 01_登別 | 0.000000 | 0.095540 | 0.000000 | 0.103714 | 0.075597 | 0.093931 | 0.127111 | 0.065581 | 0.000000 | 0.000000 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
| 02_草津 | 0.096052 | 0.107069 | 0.000000 | 0.135544 | 0.077708 | 0.112009 | 0.148662 | 0.078877 | 0.000000 | 0.063178 | ... | 0.000000 | 0.002950 | 0.003941 | 0.002962 | 0.007913 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.004926 |
| 03_箱根 | 0.108022 | 0.103535 | 0.000000 | 0.117418 | 0.078131 | 0.140490 | 0.107884 | 0.100955 | 0.000000 | 0.074321 | ... | 0.001974 | 0.004926 | 0.005923 | 0.002962 | 0.000000 | 0.003933 | 0.000000 | 0.000000 | 0.000000 | 0.003937 |
| 04_道後 | 0.000000 | 0.000000 | 0.077842 | 0.000000 | 0.000000 | 0.000000 | 0.106354 | 0.000000 | 0.079792 | 0.063679 | ... | 0.000000 | 0.000000 | 0.003941 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
| 05_湯布院 | 0.096914 | 0.092572 | 0.000000 | 0.107206 | 0.082808 | 0.139631 | 0.112500 | 0.109024 | 0.000000 | 0.066698 | ... | 0.000000 | 0.006910 | 0.001967 | 0.005941 | 0.002953 | 0.004921 | 0.000000 | 0.000000 | 0.000000 | 0.005917 |
| 06_札幌 | 0.090057 | 0.000000 | 0.096561 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.083080 | 0.000000 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.002953 | 0.003933 | 0.000000 | 0.003941 | 0.002950 | 0.000000 |
| 07_名古屋 | 0.000000 | 0.000000 | 0.092388 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.070507 | 0.061176 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.003937 | 0.001967 | 0.004926 | 0.000000 |
| 08_東京 | 0.000000 | 0.000000 | 0.095800 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.074202 | 0.000000 | ... | 0.002964 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.004926 | 0.000000 | 0.000000 | 0.000000 |
| 09_大阪 | 0.089631 | 0.000000 | 0.090501 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.079792 | 0.000000 | ... | 0.004950 | 0.000000 | 0.001967 | 0.000000 | 0.002953 | 0.000000 | 0.000000 | 0.003941 | 0.000000 | 0.000000 |
| 10_福岡 | 0.090909 | 0.000000 | 0.099234 | 0.000000 | 0.071401 | 0.000000 | 0.000000 | 0.000000 | 0.102258 | 0.000000 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.002947 | 0.000000 | 0.006917 | 0.007905 | 0.000000 |
12 rows × 1000 columns
2.1.6 共起ネットワーク図¶
In [18]:
# 必要ライブラリのインポート
from scipy.sparse import csc_matrix
import matplotlib.pyplot as plt
%matplotlib inline
# サブルーチン
def sort_and_plot(name, group):
# 「カテゴリー」ごとに Jaccard 係数でソートする
sorted_columns = np.argsort(jaccard_attrs_df.loc[name].values)[::-1][:75]
# Jaccard 係数Top 75語をソートして抽出する
group_cross_df = group.iloc[:,sorted_columns]
# 共起行列を作成する
X = group_cross_df.values
X = csc_matrix(X)
Xc = (X.T * X)
Xc = np.triu(Xc.toarray())
# 共起行列を DataFrame に整える
group_cooccur_df = pd.DataFrame(Xc, columns=group_cross_df.columns, index=group_cross_df.columns)
# 共起行列の中身を Jaccard 係数に入れ替える
group_jaccard_df = gssm_utils.jaccard_coef(group_cooccur_df, group_cross_df)
# 抽出語の出現回数を取得する
word_counts = group_cross_df.sum(axis=0).values
# プロットする
ax = fig.add_subplot(4, 3, i+1)
gssm_utils.plot_cooccur_network_ax(ax, group_jaccard_df, word_counts, np.sort(group_jaccard_df.values.reshape(-1))[::-1][120], html=True)
ax.set_title(name)
# プロットの準備
# fig = plt.figure(figsize=(20, 28))
fig = plt.figure(figsize=(28, 28))
i = 0
# カテゴリごとのループ
for name, group in cross_1000_df.groupby(level='カテゴリー'):
# サブルーチンを呼ぶ
sort_and_plot(name, group)
i += 1
# エリアごとのループ
for sub_name, sub_group in group.groupby(level='エリア'):
# サブルーチンを呼ぶ
sort_and_plot(sub_name, sub_group)
i += 1
# プロットの仕上げ
plt.tight_layout()
plt.show()
plot_cooccur_network-20240707133409.450.html plot_cooccur_network-20240707133409.914.html plot_cooccur_network-20240707133410.342.html plot_cooccur_network-20240707133410.733.html plot_cooccur_network-20240707133411.637.html plot_cooccur_network-20240707133412.068.html plot_cooccur_network-20240707133412.635.html plot_cooccur_network-20240707133413.647.html plot_cooccur_network-20240707133414.533.html plot_cooccur_network-20240707133415.378.html plot_cooccur_network-20240707133416.132.html plot_cooccur_network-20240707133416.798.html
1.3 高評価のエリアに倣って、低評価のエリアを改善するプランを提案する¶
1.2.0 対照的な2エリアを選択する¶
In [19]:
# コーディングルール
coding_pos = ["良い","美味しい","広い","多い","素晴らしい","嬉しい","気持ちよい","楽しい","近い","大きい","気持ち良い","温かい","早い","優しい","新しい","暖かい","快い","明るい","美しい","可愛い","満足"]
coding_neg = ["古い","無い","高い","悪い","小さい","狭い","少ない","寒い","遅い","熱い","欲しい","暑い","冷たい","遠い","臭い","暗い","うるさい","ない","無い","残念","改善","不満"]
In [20]:
# DataFrame を初期化する
cross_1000_ps_df = cross_1000_df.copy()
cross_1000_ps_df['ポジ'] = 0
cross_1000_ps_df['ネガ'] = 0
cross_1000_ps_df['総合1-2'] = 0
cross_1000_ps_df['総合4-5'] = 0
# コーディングルールを適用する (ポジ・ネガ)
pos_index = docs_df['表層'].str.contains("|".join(coding_pos))
neg_index = docs_df['表層'].str.contains("|".join(coding_neg))
cross_1000_ps_df.loc[cross_1000_ps_df.index.get_level_values('文書ID').isin(docs_df.loc[pos_index, '文書ID']), 'ポジ'] = 1
cross_1000_ps_df.loc[cross_1000_ps_df.index.get_level_values('文書ID').isin(docs_df.loc[neg_index, '文書ID']), 'ネガ'] = 1
# コーディングルールを適用する (総合評価)
cross_1000_ps_df.loc[cross_1000_ps_df.index.get_level_values('文書ID').isin(all_df[all_df['総合'] <=2].index), '総合1-2'] = 1
cross_1000_ps_df.loc[cross_1000_ps_df.index.get_level_values('文書ID').isin(all_df[all_df['総合'] >=4].index), '総合4-5'] = 1
cross_1000_ps_df = cross_1000_ps_df[['ポジ','ネガ','総合1-2','総合4-5']]
# DataFrame を表示する
print(cross_1000_ps_df.shape)
display(cross_1000_ps_df)
(9914, 4)
| 表層 | ポジ | ネガ | 総合1-2 | 総合4-5 | ||
|---|---|---|---|---|---|---|
| カテゴリー | エリア | 文書ID | ||||
| A_レジャー | 01_登別 | 1 | 1 | 0 | 0 | 1 |
| 2 | 1 | 1 | 0 | 1 | ||
| 3 | 0 | 0 | 0 | 1 | ||
| 4 | 1 | 0 | 1 | 0 | ||
| 5 | 1 | 1 | 0 | 1 | ||
| ... | ... | ... | ... | ... | ... | ... |
| B_ビジネス | 10_福岡 | 9996 | 1 | 0 | 0 | 1 |
| 9997 | 0 | 1 | 0 | 1 | ||
| 9998 | 0 | 0 | 0 | 1 | ||
| 9999 | 1 | 0 | 0 | 1 | ||
| 10000 | 1 | 0 | 0 | 0 |
9914 rows × 4 columns
In [21]:
# 「カテゴリー」のクロス集計と「エリア」のクロス集計を連結する
aggregate_ps_df = pd.concat(
[
cross_1000_ps_df.groupby(level='カテゴリー').sum(),
cross_1000_ps_df.groupby(level='エリア').sum()
]
)
# DataFrame を表示する
print(aggregate_ps_df.shape)
display(aggregate_ps_df)
(12, 4)
| 表層 | ポジ | ネガ | 総合1-2 | 総合4-5 |
|---|---|---|---|---|
| A_レジャー | 3798 | 2297 | 282 | 4279 |
| B_ビジネス | 3230 | 2105 | 289 | 4032 |
| 01_登別 | 729 | 472 | 69 | 822 |
| 02_草津 | 803 | 497 | 56 | 843 |
| 03_箱根 | 793 | 495 | 63 | 857 |
| 04_道後 | 668 | 405 | 47 | 854 |
| 05_湯布院 | 805 | 428 | 47 | 903 |
| 06_札幌 | 666 | 399 | 48 | 825 |
| 07_名古屋 | 642 | 435 | 40 | 828 |
| 08_東京 | 641 | 421 | 70 | 776 |
| 09_大阪 | 655 | 409 | 52 | 815 |
| 10_福岡 | 626 | 441 | 79 | 788 |
In [22]:
# 必要ライブラリのインポート
import mca
# ライブラリ mca による対応分析
ncols = aggregate_ps_df.shape[1]
mca_ben = mca.MCA(aggregate_ps_df, ncols=ncols, benzecri=False)
# 行方向および列方向の値を取り出す
row_coord = mca_ben.fs_r(N=2)
col_coord = mca_ben.fs_c(N=2)
# 固有値を求める
eigenvalues = mca_ben.L
total = np.sum(eigenvalues)
# 寄与率を求める
explained_inertia = 100 * eigenvalues / total
# 行方向および列方向のラベルを取得する
row_labels = aggregate_ps_df.index
col_labels = aggregate_ps_df.columns
# プロットする
gssm_utils.plot_coresp(row_coord, col_coord, row_labels, col_labels, explained_inertia)
1.2.1 ポジティブ意見の「文書-抽出語」表を作成する¶
In [23]:
# 必要ライブラリのインポート
from scipy.sparse import csc_matrix
import matplotlib.pyplot as plt
%matplotlib inline
# コーディングルール
coding_or = coding_pos
# サブルーチン
def sort_and_plot(name, group):
# 「カテゴリー」ごとに Jaccard 係数でソートする
sorted_columns = np.argsort(jaccard_attrs_df.loc[name].values)[::-1][:75]
# Jaccard 係数Top 75語をソートして抽出する
group_cross_df = group.iloc[:,sorted_columns]
# 共起行列を作成する
X = group_cross_df.values
X = csc_matrix(X)
Xc = (X.T * X)
Xc = np.triu(Xc.toarray())
# コーディングルールで絞り込む
index = docs_df['表層'].str.contains("|".join(coding_or))
group_cross_df = group_cross_df[group_cross_df.index.get_level_values('文書ID').isin(docs_df.loc[index, '文書ID'])]
# 共起行列を DataFrame に整える
group_cooccur_df = pd.DataFrame(Xc, columns=group_cross_df.columns, index=group_cross_df.columns)
# 共起行列の中身を Jaccard 係数に入れ替える
group_jaccard_df = gssm_utils.jaccard_coef(group_cooccur_df, group_cross_df)
# 抽出語の出現回数を取得する
word_counts = group_cross_df.sum(axis=0).values
# プロットする
ax = fig.add_subplot(4, 3, i+1)
gssm_utils.plot_cooccur_network_with_code_ax(ax, group_jaccard_df, word_counts, np.sort(group_jaccard_df.values.reshape(-1))[::-1][120], coding_or, html=True)
ax.set_title(name)
# プロットの準備
# fig = plt.figure(figsize=(20, 28))
fig = plt.figure(figsize=(28, 28))
i = 0
# カテゴリごとのループ
for name, group in cross_1000_df.groupby(level='カテゴリー'):
# サブルーチンを呼ぶ
sort_and_plot(name, group)
i += 1
# エリアごとのループ
for sub_name, sub_group in group.groupby(level='エリア'):
# サブルーチンを呼ぶ
sort_and_plot(sub_name, sub_group)
i += 1
# プロットの仕上げ
plt.tight_layout()
plt.show()
plot_cooccur_network_with_code-20240707133421.300.html plot_cooccur_network_with_code-20240707133421.893.html plot_cooccur_network_with_code-20240707133422.321.html plot_cooccur_network_with_code-20240707133422.871.html plot_cooccur_network_with_code-20240707133423.776.html plot_cooccur_network_with_code-20240707133424.203.html plot_cooccur_network_with_code-20240707133424.858.html plot_cooccur_network_with_code-20240707133425.783.html plot_cooccur_network_with_code-20240707133426.727.html plot_cooccur_network_with_code-20240707133427.695.html plot_cooccur_network_with_code-20240707133428.500.html plot_cooccur_network_with_code-20240707133429.089.html
1.2.2 ネガティブ意見の「文書-抽出語」表を作成する¶
In [ ]:
# 必要ライブラリのインポート
from scipy.sparse import csc_matrix
import matplotlib.pyplot as plt
%matplotlib inline
# コーディングルール
coding_or = coding_neg
# サブルーチン
def sort_and_plot(name, group):
# 「カテゴリー」ごとに Jaccard 係数でソートする
sorted_columns = np.argsort(jaccard_attrs_df.loc[name].values)[::-1][:75]
# Jaccard 係数Top 75語をソートして抽出する
group_cross_df = group.iloc[:,sorted_columns]
# 共起行列を作成する
X = group_cross_df.values
X = csc_matrix(X)
Xc = (X.T * X)
Xc = np.triu(Xc.toarray())
# コーディングルールで絞り込む
index = docs_df['表層'].str.contains("|".join(coding_or))
group_cross_df = group_cross_df[group_cross_df.index.get_level_values('文書ID').isin(docs_df.loc[index, '文書ID'])]
# 共起行列を DataFrame に整える
group_cooccur_df = pd.DataFrame(Xc, columns=group_cross_df.columns, index=group_cross_df.columns)
# 共起行列の中身を Jaccard 係数に入れ替える
group_jaccard_df = gssm_utils.jaccard_coef(group_cooccur_df, group_cross_df)
# 抽出語の出現回数を取得する
word_counts = group_cross_df.sum(axis=0).values
# プロットする
ax = fig.add_subplot(4, 3, i+1)
gssm_utils.plot_cooccur_network_with_code_ax(ax, group_jaccard_df, word_counts, np.sort(group_jaccard_df.values.reshape(-1))[::-1][120], coding_or, html=True)
ax.set_title(name)
# プロットの準備
# fig = plt.figure(figsize=(20, 28))
fig = plt.figure(figsize=(28, 28))
i = 0
# カテゴリごとのループ
for name, group in cross_1000_df.groupby(level='カテゴリー'):
# サブルーチンを呼ぶ
sort_and_plot(name, group)
i += 1
# エリアごとのループ
for sub_name, sub_group in group.groupby(level='エリア'):
# サブルーチンを呼ぶ
sort_and_plot(sub_name, sub_group)
i += 1
# プロットの仕上げ
plt.tight_layout()
plt.show()
plot_cooccur_network_with_code-20240707133432.739.html plot_cooccur_network_with_code-20240707133433.233.html plot_cooccur_network_with_code-20240707133433.699.html plot_cooccur_network_with_code-20240707133434.033.html plot_cooccur_network_with_code-20240707133434.792.html plot_cooccur_network_with_code-20240707133435.095.html plot_cooccur_network_with_code-20240707133435.485.html plot_cooccur_network_with_code-20240707133436.501.html plot_cooccur_network_with_code-20240707133437.238.html plot_cooccur_network_with_code-20240707133437.810.html plot_cooccur_network_with_code-20240707133438.432.html plot_cooccur_network_with_code-20240707133438.881.html
1.2.3 本文の参照 (カテゴリーごと)¶
「登別」と「道後」で「すばらしい」という単語が含まれている口コミを表示する
In [ ]:
# 検索条件
search_index = \
all_df['エリア'].isin(['01_登別', '05_道後']) & \
(all_df['コメント'].str.contains('素晴らしい') | all_df['コメント'].str.contains('すばらしい'))
# 検索する
result_df = all_df[search_index]
# DataFrame を表示する
print(result_df.shape)
display(result_df.head())
# CSV に保存する
result_df.to_csv("output-1.csv", header=True)
「東京」と「福岡」で「うるさい」という単語が含まれている口コミを表示する
In [ ]:
# 検索条件
search_index = \
all_df['エリア'].isin(['08_東京', '10_福岡']) & \
all_df['コメント'].str.contains('うるさい')
# 検索する
result_df = all_df[search_index]
# DataFrame を表示する
print(result_df.shape)
display(result_df.head())
# CSV に保存する
result_df.to_csv("output-2.csv", header=True)